# -*- coding: utf-8 -*-
"""
    sphinx.writers.latex
    ~~~~~~~~~~~~~~~~~~~~

    Custom docutils writer for LaTeX.

    Much of this code is adapted from Dave Kuhlman's "docpy" writer from his
    docutils sandbox.

    :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import re
import sys
from os import path

from six import itervalues, text_type
from docutils import nodes, writers
from docutils.writers.latex2e import Babel

from sphinx import addnodes
from sphinx import highlighting
from sphinx.errors import SphinxError
from sphinx.locale import admonitionlabels, _
from sphinx.util import split_into
from sphinx.util.osutil import ustrftime
from sphinx.util.texescape import tex_escape_map, tex_replace_map
from sphinx.util.smartypants import educate_quotes_latex

HEADER = r'''%% Generated by Sphinx.
\def\sphinxdocclass{%(docclass)s}
\documentclass[%(papersize)s,%(pointsize)s%(classoptions)s]{%(wrapperclass)s}
%(inputenc)s
%(utf8extra)s
%(cmappkg)s
%(fontenc)s
%(babel)s
%(fontpkg)s
%(fncychap)s
%(longtable)s
\usepackage{sphinx}
\usepackage{multirow}
%(preamble)s

\title{%(title)s}
\date{%(date)s}
\release{%(release)s}
\author{%(author)s}
\newcommand{\sphinxlogo}{%(logo)s}
\renewcommand{\releasename}{%(releasename)s}
%(makeindex)s
'''

BEGIN_DOC = r'''
\begin{document}
%(shorthandoff)s
%(maketitle)s
%(tableofcontents)s
'''

FOOTER = r'''
\renewcommand{\indexname}{%(indexname)s}
%(printindex)s
\end{document}
'''

class collected_footnote(nodes.footnote):
    """Footnotes that are collected are assigned this class."""

class UnsupportedError(SphinxError):
    category = 'Markup is unsupported in LaTeX'


class LaTeXWriter(writers.Writer):

    supported = ('sphinxlatex',)

    settings_spec = ('LaTeX writer options', '', (
        ('Document name', ['--docname'], {'default': ''}),
        ('Document class', ['--docclass'], {'default': 'manual'}),
        ('Author', ['--author'], {'default': ''}),
        ))
    settings_defaults = {}

    output = None

    def __init__(self, builder):
        writers.Writer.__init__(self)
        self.builder = builder
        self.translator_class = (
            self.builder.translator_class or LaTeXTranslator)

    def translate(self):
        visitor = self.translator_class(self.document, self.builder)
        self.document.walkabout(visitor)
        self.output = visitor.astext()


# Helper classes

class ExtBabel(Babel):
    def get_shorthandoff(self):
        shortlang = self.language.split('_')[0]
        if shortlang in ('de', 'ngerman', 'sl', 'slovene', 'pt', 'portuges',
                         'es', 'spanish', 'nl', 'dutch', 'pl', 'polish', 'it',
                         'italian'):
            return '\\shorthandoff{"}'
        return ''

    def uses_cyrillic(self):
        shortlang = self.language.split('_')[0]
        return shortlang in ('bg','bulgarian', 'kk','kazakh',
                             'mn','mongolian', 'ru','russian',
                             'uk','ukrainian')

# in latest trunk, the attribute is called Babel.language_codes and already
# includes Slovene
if hasattr(Babel, '_ISO639_TO_BABEL'):
    Babel._ISO639_TO_BABEL['sl'] = 'slovene'


class Table(object):
    def __init__(self):
        self.col = 0
        self.colcount = 0
        self.colspec = None
        self.rowcount = 0
        self.had_head = False
        self.has_problematic = False
        self.has_verbatim = False
        self.caption = None
        self.longtable = False


class LaTeXTranslator(nodes.NodeVisitor):
    sectionnames = ["part", "chapter", "section", "subsection",
                    "subsubsection", "paragraph", "subparagraph"]

    ignore_missing_images = False

    default_elements = {
        'papersize':       'letterpaper',
        'pointsize':       '10pt',
        'classoptions':    '',
        'extraclassoptions': '',
        'inputenc':        '\\usepackage[utf8]{inputenc}',
        'utf8extra':       '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}',
        'cmappkg':         '\\usepackage{cmap}',
        'fontenc':         '\\usepackage[T1]{fontenc}',
        'babel':           '\\usepackage{babel}',
        'fontpkg':         '\\usepackage{times}',
        'fncychap':        '\\usepackage[Bjarne]{fncychap}',
        'longtable':       '\\usepackage{longtable}',
        'preamble':        '',
        'title':           '',
        'date':            '',
        'release':         '',
        'author':          '',
        'logo':            '',
        'releasename':     'Release',
        'makeindex':       '\\makeindex',
        'shorthandoff':    '',
        'maketitle':       '\\maketitle',
        'tableofcontents': '\\tableofcontents',
        'footer':          '',
        'printindex':      '\\printindex',
        'transition':      '\n\n\\bigskip\\hrule{}\\bigskip\n\n',
        'figure_align':    'htbp',
    }

    # sphinx specific document classes
    docclasses = ('howto', 'manual')

    def __init__(self, document, builder):
        nodes.NodeVisitor.__init__(self, document)
        self.builder = builder
        self.body = []

        # sort out some elements
        papersize = builder.config.latex_paper_size + 'paper'
        if papersize == 'paper': # e.g. command line "-D latex_paper_size="
            papersize = 'letterpaper'

        self.elements = self.default_elements.copy()
        self.elements.update({
            'wrapperclass': self.format_docclass(document.settings.docclass),
            'papersize':    papersize,
            'pointsize':    builder.config.latex_font_size,
            # if empty, the title is set to the first section title
            'title':        document.settings.title,
            'release':      builder.config.release,
            'author':       document.settings.author,
            'releasename':  _('Release'),
            'preamble':     builder.config.latex_preamble,
            'indexname':    _('Index'),
        })
        if document.settings.docclass == 'howto':
            docclass = builder.config.latex_docclass.get('howto', 'article')
        else:
            docclass = builder.config.latex_docclass.get('manual', 'report')
        self.elements['docclass'] = docclass
        if builder.config.today:
            self.elements['date'] = builder.config.today
        else:
            self.elements['date'] = ustrftime(builder.config.today_fmt
                                              or _('%B %d, %Y'))
        if builder.config.latex_logo:
            self.elements['logo'] = '\\includegraphics{%s}\\par' % \
                                    path.basename(builder.config.latex_logo)
        if builder.config.language:
            babel = ExtBabel(builder.config.language)
            lang = babel.get_language()
            if lang:
                self.elements['classoptions'] += ',' + babel.get_language()
            else:
                self.builder.warn('no Babel option known for language %r' %
                                  builder.config.language)
            self.elements['shorthandoff'] = babel.get_shorthandoff()
            self.elements['fncychap'] = '\\usepackage[Sonny]{fncychap}'

            # Times fonts don't work with Cyrillic languages
            if babel.uses_cyrillic():
                self.elements['fontpkg'] = ''

            # pTeX (Japanese TeX) for support
            if builder.config.language == 'ja':
                # use dvipdfmx as default class option in Japanese
                self.elements['classoptions'] = ',dvipdfmx'
                # disable babel which has not publishing quality in Japanese
                self.elements['babel'] = ''
                # disable fncychap in Japanese documents
                self.elements['fncychap'] = ''
        else:
            self.elements['classoptions'] += ',english'
        # allow the user to override them all
        self.elements.update(builder.config.latex_elements)
        if self.elements['extraclassoptions']:
            self.elements['classoptions'] += ',' + \
                                             self.elements['extraclassoptions']

        self.highlighter = highlighting.PygmentsBridge('latex',
            builder.config.pygments_style, builder.config.trim_doctest_flags)
        self.context = []
        self.descstack = []
        self.bibitems = []
        self.table = None
        self.next_table_colspec = None
        # stack of [language, linenothreshold] settings per file
        # the first item here is the default and must not be changed
        # the second item is the default for the master file and can be changed
        # by .. highlight:: directive in the master file
        self.hlsettingstack = 2 * [[builder.config.highlight_language,
                                    sys.maxsize]]
        self.footnotestack = []
        self.curfilestack = []
        self.handled_abbrs = set()
        if document.settings.docclass == 'howto':
            self.top_sectionlevel = 2
        else:
            if builder.config.latex_use_parts:
                self.top_sectionlevel = 0
            else:
                self.top_sectionlevel = 1
        self.next_section_ids = set()
        self.next_figure_ids = set()
        self.next_table_ids = set()
        # flags
        self.in_title = 0
        self.in_production_list = 0
        self.in_footnote = 0
        self.in_caption = 0
        self.first_document = 1
        self.this_is_the_title = 1
        self.literal_whitespace = 0
        self.no_contractions = 0
        self.compact_list = 0
        self.first_param = 0
        self.previous_spanning_row = 0
        self.previous_spanning_column = 0
        self.remember_multirow = {}

    def format_docclass(self, docclass):
        """ prepends prefix to sphinx document classes
        """
        if docclass in self.docclasses:
            docclass = 'sphinx' + docclass
        return docclass

    def astext(self):
        return (HEADER % self.elements +
                self.highlighter.get_stylesheet() +
                u''.join(self.body) +
                '\n' + self.elements['footer'] + '\n' +
                self.generate_indices() +
                FOOTER % self.elements)

    def hypertarget(self, id, withdoc=True, anchor=True):
        if withdoc:
            id = self.curfilestack[-1] + ':' + id
        return (anchor and '\\phantomsection' or '') + \
               '\\label{%s}' % self.idescape(id)

    def hyperlink(self, id):
        return '{\\hyperref[%s]{' % self.idescape(id)

    def hyperpageref(self, id):
        return '\\autopageref*{%s}' % self.idescape(id)

    def idescape(self, id):
        return text_type(id).translate(tex_replace_map).\
            encode('ascii', 'backslashreplace').decode('ascii').\
            replace('\\', '_')

    def generate_indices(self):
        def generate(content, collapsed):
            ret.append('\\begin{theindex}\n')
            ret.append('\\def\\bigletter#1{{\\Large\\sffamily#1}'
                       '\\nopagebreak\\vspace{1mm}}\n')
            for i, (letter, entries) in enumerate(content):
                if i > 0:
                    ret.append('\\indexspace\n')
                ret.append('\\bigletter{%s}\n' %
                           text_type(letter).translate(tex_escape_map))
                for entry in entries:
                    if not entry[3]:
                        continue
                    ret.append('\\item {\\texttt{%s}}' % self.encode(entry[0]))
                    if entry[4]:
                        # add "extra" info
                        ret.append(' \\emph{(%s)}' % self.encode(entry[4]))
                    ret.append(', \\pageref{%s:%s}\n' %
                               (entry[2], self.idescape(entry[3])))
            ret.append('\\end{theindex}\n')

        ret = []
        # latex_domain_indices can be False/True or a list of index names
        indices_config = self.builder.config.latex_domain_indices
        if indices_config:
            for domain in itervalues(self.builder.env.domains):
                for indexcls in domain.indices:
                    indexname = '%s-%s' % (domain.name, indexcls.name)
                    if isinstance(indices_config, list):
                        if indexname not in indices_config:
                            continue
                    # deprecated config value
                    if indexname == 'py-modindex' and \
                           not self.builder.config.latex_use_modindex:
                        continue
                    content, collapsed = indexcls(domain).generate(
                        self.builder.docnames)
                    if not content:
                        continue
                    ret.append(u'\\renewcommand{\\indexname}{%s}\n' %
                               indexcls.localname)
                    generate(content, collapsed)

        return ''.join(ret)

    def visit_document(self, node):
        self.footnotestack.append(self.collect_footnotes(node))
        self.curfilestack.append(node.get('docname', ''))
        if self.first_document == 1:
            # the first document is all the regular content ...
            self.body.append(BEGIN_DOC % self.elements)
            self.first_document = 0
        elif self.first_document == 0:
            # ... and all others are the appendices
            self.body.append(u'\n\\appendix\n')
            self.first_document = -1
        if 'docname' in node:
            self.body.append(self.hypertarget(':doc'))
        # "- 1" because the level is increased before the title is visited
        self.sectionlevel = self.top_sectionlevel - 1
    def depart_document(self, node):
        if self.bibitems:
            widest_label = ""
            for bi in self.bibitems:
                if len(widest_label) < len(bi[0]):
                    widest_label = bi[0]
            self.body.append(u'\n\\begin{thebibliography}{%s}\n' % widest_label)
            for bi in self.bibitems:
                target = self.hypertarget(bi[2] + ':' + bi[3],
                                          withdoc=False)
                self.body.append(u'\\bibitem[%s]{%s}{%s %s}\n' %
                    (self.encode(bi[0]), self.idescape(bi[0]), target, bi[1]))
            self.body.append(u'\\end{thebibliography}\n')
            self.bibitems = []

    def visit_start_of_file(self, node):
        # collect new footnotes
        self.footnotestack.append(self.collect_footnotes(node))
        # also add a document target
        self.next_section_ids.add(':doc')
        self.curfilestack.append(node['docname'])
        # use default highlight settings for new file
        self.hlsettingstack.append(self.hlsettingstack[0])

    def collect_footnotes(self, node):
        fnotes = {}
        def footnotes_under(n):
            if isinstance(n, nodes.footnote):
                yield n
            else:
                for c in n.children:
                    if isinstance(c, addnodes.start_of_file):
                        continue
                    for k in footnotes_under(c):
                        yield k
        for fn in footnotes_under(node):
            num = fn.children[0].astext().strip()
            fnotes[num] = [collected_footnote(*fn.children), False]
        return fnotes

    def depart_start_of_file(self, node):
        self.footnotestack.pop()
        self.curfilestack.pop()
        self.hlsettingstack.pop()

    def visit_highlightlang(self, node):
        self.hlsettingstack[-1] = [node['lang'], node['linenothreshold']]
        raise nodes.SkipNode

    def visit_section(self, node):
        if not self.this_is_the_title:
            self.sectionlevel += 1
        self.body.append('\n\n')
        if node.get('ids'):
            self.next_section_ids.update(node['ids'])
    def depart_section(self, node):
        self.sectionlevel = max(self.sectionlevel - 1,
                                self.top_sectionlevel - 1)

    def visit_problematic(self, node):
        self.body.append(r'{\color{red}\bfseries{}')
    def depart_problematic(self, node):
        self.body.append('}')

    def visit_topic(self, node):
        self.body.append('\\setbox0\\vbox{\n'
                         '\\begin{minipage}{0.95\\linewidth}\n')
    def depart_topic(self, node):
        self.body.append('\\end{minipage}}\n'
                         '\\begin{center}\\setlength{\\fboxsep}{5pt}'
                         '\\shadowbox{\\box0}\\end{center}\n')
    visit_sidebar = visit_topic
    depart_sidebar = depart_topic

    def visit_glossary(self, node):
        pass
    def depart_glossary(self, node):
        pass

    def visit_productionlist(self, node):
        self.body.append('\n\n\\begin{productionlist}\n')
        self.in_production_list = 1
    def depart_productionlist(self, node):
        self.body.append('\\end{productionlist}\n\n')
        self.in_production_list = 0

    def visit_production(self, node):
        if node['tokenname']:
            tn = node['tokenname']
            self.body.append(self.hypertarget('grammar-token-' + tn))
            self.body.append('\\production{%s}{' % self.encode(tn))
        else:
            self.body.append('\\productioncont{')
    def depart_production(self, node):
        self.body.append('}\n')

    def visit_transition(self, node):
        self.body.append(self.elements['transition'])
    def depart_transition(self, node):
        pass

    def visit_title(self, node):
        parent = node.parent
        if isinstance(parent, addnodes.seealso):
            # the environment already handles this
            raise nodes.SkipNode
        elif self.this_is_the_title:
            if len(node.children) != 1 and not isinstance(node.children[0],
                                                          nodes.Text):
                self.builder.warn('document title is not a single Text node',
                                  (self.curfilestack[-1], node.line))
            if not self.elements['title']:
                # text needs to be escaped since it is inserted into
                # the output literally
                self.elements['title'] = node.astext().translate(tex_escape_map)
            self.this_is_the_title = 0
            raise nodes.SkipNode
        elif isinstance(parent, nodes.section):
            try:
                self.body.append(r'\%s{' % self.sectionnames[self.sectionlevel])
            except IndexError:
                # just use "subparagraph", it's not numbered anyway
                self.body.append(r'\%s{' % self.sectionnames[-1])
            self.context.append('}\n')

            if self.next_section_ids:
                for id in self.next_section_ids:
                    self.context[-1] += self.hypertarget(id, anchor=False)
                self.next_section_ids.clear()

        elif isinstance(parent, (nodes.topic, nodes.sidebar)):
            self.body.append(r'\textbf{')
            self.context.append('}\n\n\medskip\n\n')
        elif isinstance(parent, nodes.Admonition):
            self.body.append('{')
            self.context.append('}\n')
        elif isinstance(parent, nodes.table):
            self.table.caption = self.encode(node.astext())
            raise nodes.SkipNode
        else:
            self.builder.warn(
                'encountered title node not in section, topic, table, '
                'admonition or sidebar',
                (self.curfilestack[-1], node.line or ''))
            self.body.append('\\textbf{')
            self.context.append('}\n')
        self.in_title = 1
    def depart_title(self, node):
        self.in_title = 0
        self.body.append(self.context.pop())

    def visit_subtitle(self, node):
        if isinstance(node.parent, nodes.sidebar):
            self.body.append('~\\\\\n\\textbf{')
            self.context.append('}\n\\smallskip\n')
        else:
            self.context.append('')
    def depart_subtitle(self, node):
        self.body.append(self.context.pop())

    def visit_desc(self, node):
        self.body.append('\n\n\\begin{fulllineitems}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_desc(self, node):
        self.body.append('\n\\end{fulllineitems}\n\n')

    def visit_desc_signature(self, node):
        if node.parent['objtype'] != 'describe' and node['ids']:
            hyper = self.hypertarget(node['ids'][0])
        else:
            hyper = ''
        self.body.append(hyper)
        for child in node:
            if isinstance(child, addnodes.desc_parameterlist):
                self.body.append(r'\pysiglinewithargsret{')
                break
        else:
            self.body.append(r'\pysigline{')
    def depart_desc_signature(self, node):
        self.body.append('}')

    def visit_desc_addname(self, node):
        self.body.append(r'\code{')
        self.literal_whitespace += 1
    def depart_desc_addname(self, node):
        self.body.append('}')
        self.literal_whitespace -= 1

    def visit_desc_type(self, node):
        pass
    def depart_desc_type(self, node):
        pass

    def visit_desc_returns(self, node):
        self.body.append(r'{ $\rightarrow$ ')
    def depart_desc_returns(self, node):
        self.body.append(r'}')

    def visit_desc_name(self, node):
        self.body.append(r'\bfcode{')
        self.no_contractions += 1
        self.literal_whitespace += 1
    def depart_desc_name(self, node):
        self.body.append('}')
        self.literal_whitespace -= 1
        self.no_contractions -= 1

    def visit_desc_parameterlist(self, node):
        # close name, open parameterlist
        self.body.append('}{')
        self.first_param = 1
    def depart_desc_parameterlist(self, node):
        # close parameterlist, open return annotation
        self.body.append('}{')

    def visit_desc_parameter(self, node):
        if not self.first_param:
            self.body.append(', ')
        else:
            self.first_param = 0
        if not node.hasattr('noemph'):
            self.body.append(r'\emph{')
    def depart_desc_parameter(self, node):
        if not node.hasattr('noemph'):
            self.body.append('}')

    def visit_desc_optional(self, node):
        self.body.append(r'\optional{')
    def depart_desc_optional(self, node):
        self.body.append('}')

    def visit_desc_annotation(self, node):
        self.body.append(r'\strong{')
    def depart_desc_annotation(self, node):
        self.body.append('}')

    def visit_desc_content(self, node):
        if node.children and not isinstance(node.children[0], nodes.paragraph):
            # avoid empty desc environment which causes a formatting bug
            self.body.append('~')
    def depart_desc_content(self, node):
        pass

    def visit_seealso(self, node):
        self.body.append(u'\n\n\\strong{%s:}\n\n' % admonitionlabels['seealso'])
    def depart_seealso(self, node):
        self.body.append("\n\n")

    def visit_rubric(self, node):
        if len(node.children) == 1 and node.children[0].astext() in \
               ('Footnotes', _('Footnotes')):
            raise nodes.SkipNode
        self.body.append('\\paragraph{')
        self.context.append('}\n')
    def depart_rubric(self, node):
        self.body.append(self.context.pop())

    def visit_footnote(self, node):
        raise nodes.SkipNode

    def visit_collected_footnote(self, node):
        self.in_footnote += 1
        self.body.append('\\footnote{')
    def depart_collected_footnote(self, node):
        self.body.append('}')
        self.in_footnote -= 1

    def visit_label(self, node):
        if isinstance(node.parent, nodes.citation):
            self.bibitems[-1][0] = node.astext()
            self.bibitems[-1][2] = self.curfilestack[-1]
            self.bibitems[-1][3] = node.parent['ids'][0]
        raise nodes.SkipNode

    def visit_tabular_col_spec(self, node):
        self.next_table_colspec = node['spec']
        raise nodes.SkipNode

    def visit_table(self, node):
        if self.table:
            raise UnsupportedError(
                '%s:%s: nested tables are not yet implemented.' %
                (self.curfilestack[-1], node.line or ''))
        self.table = Table()
        self.table.longtable = 'longtable' in node['classes']
        self.tablebody = []
        self.tableheaders = []
        # Redirect body output until table is finished.
        self._body = self.body
        self.body = self.tablebody
    def depart_table(self, node):
        if self.table.rowcount > 30:
            self.table.longtable = True
        self.body = self._body
        if not self.table.longtable and self.table.caption is not None:
            self.body.append(u'\n\n\\begin{threeparttable}\n'
                             u'\\capstart\\caption{%s}\n' % self.table.caption)
        if self.table.longtable:
            self.body.append('\n\\begin{longtable}')
            endmacro = '\\end{longtable}\n\n'
        elif self.table.has_verbatim:
            self.body.append('\n\\begin{tabular}')
            endmacro = '\\end{tabular}\n\n'
        elif self.table.has_problematic and not self.table.colspec:
            # if the user has given us tabularcolumns, accept them and use
            # tabulary nevertheless
            self.body.append('\n\\begin{tabular}')
            endmacro = '\\end{tabular}\n\n'
        else:
            self.body.append('\n\\begin{tabulary}{\\linewidth}')
            endmacro = '\\end{tabulary}\n\n'
        if self.table.colspec:
            self.body.append(self.table.colspec)
        else:
            if self.table.has_problematic:
                colwidth = 0.95 / self.table.colcount
                colspec = ('p{%.3f\\linewidth}|' % colwidth) * \
                          self.table.colcount
                self.body.append('{|' + colspec + '}\n')
            elif self.table.longtable:
                self.body.append('{|' + ('l|' * self.table.colcount) + '}\n')
            else:
                self.body.append('{|' + ('L|' * self.table.colcount) + '}\n')
        if self.table.longtable and self.table.caption is not None:
            self.body.append(u'\\caption{%s} \\\\\n' % self.table.caption)
        if self.table.caption is not None:
            for id in self.next_table_ids:
                self.body.append(self.hypertarget(id, anchor=False))
            self.next_table_ids.clear()
        if self.table.longtable:
            self.body.append('\\hline\n')
            self.body.extend(self.tableheaders)
            self.body.append('\\endfirsthead\n\n')
            self.body.append('\\multicolumn{%s}{c}%%\n' % self.table.colcount)
            self.body.append(r'{{\textsf{\tablename\ \thetable{} -- %s}}} \\'
                             % _('continued from previous page'))
            self.body.append('\n\\hline\n')
            self.body.extend(self.tableheaders)
            self.body.append('\\endhead\n\n')
            self.body.append(r'\hline \multicolumn{%s}{|r|}{{\textsf{%s}}} \\ \hline'
                             % (self.table.colcount,
                                _('Continued on next page')))
            self.body.append('\n\\endfoot\n\n')
            self.body.append('\\endlastfoot\n\n')
        else:
            self.body.append('\\hline\n')
            self.body.extend(self.tableheaders)
        self.body.extend(self.tablebody)
        self.body.append(endmacro)
        if not self.table.longtable and self.table.caption is not None:
            self.body.append('\\end{threeparttable}\n\n')
        self.table = None
        self.tablebody = None

    def visit_colspec(self, node):
        self.table.colcount += 1
    def depart_colspec(self, node):
        pass

    def visit_tgroup(self, node):
        pass
    def depart_tgroup(self, node):
        pass

    def visit_thead(self, node):
        self.table.had_head = True
        if self.next_table_colspec:
            self.table.colspec = '{%s}\n' % self.next_table_colspec
        self.next_table_colspec = None
        # Redirect head output until header is finished. see visit_tbody.
        self.body = self.tableheaders
    def depart_thead(self, node):
        pass

    def visit_tbody(self, node):
        if not self.table.had_head:
            self.visit_thead(node)
        self.body = self.tablebody
    def depart_tbody(self, node):
        pass

    def visit_row(self, node):
        self.table.col = 0
    def depart_row(self, node):
        if self.previous_spanning_row == 1:
            self.previous_spanning_row = 0
        self.body.append('\\\\\n')
        self.body.append('\\hline')
        self.table.rowcount += 1

    def visit_entry(self, node):
        if self.table.col > 0:
            self.body.append(' & ')
        elif self.remember_multirow.get(1, 0) > 1:
            self.remember_multirow[1] -= 1
            self.body.append(' & ')
        self.table.col += 1
        context = ''
        if 'morerows' in node:
            self.body.append(' \multirow{')
            self.previous_spanning_row = 1
            self.body.append(str(node.get('morerows') + 1))
            self.body.append('}{*}{')
            context += '}'
            self.remember_multirow[self.table.col] = node.get('morerows') + 1
        if 'morecols' in node:
            self.body.append(' \multicolumn{')
            self.body.append(str(node.get('morecols') + 1))
            if self.table.col == 1:
                self.body.append('}{|l|}{')
            else:
                self.body.append('}{l|}{')
            context += '}'
        if isinstance(node.parent.parent, nodes.thead):
            self.body.append('\\textsf{\\relax ')
            context += '}'
        if self.remember_multirow.get(self.table.col + 1, 0) > 1:
            self.remember_multirow[self.table.col + 1] -= 1
            context += ' & '
        self.context.append(context)
    def depart_entry(self, node):
        self.body.append(self.context.pop()) # header

    def visit_acks(self, node):
        # this is a list in the source, but should be rendered as a
        # comma-separated list here
        self.body.append('\n\n')
        self.body.append(', '.join(n.astext()
                                   for n in node.children[0].children) + '.')
        self.body.append('\n\n')
        raise nodes.SkipNode

    def visit_bullet_list(self, node):
        if not self.compact_list:
            self.body.append('\\begin{itemize}\n' )
        if self.table:
            self.table.has_problematic = True
    def depart_bullet_list(self, node):
        if not self.compact_list:
            self.body.append('\\end{itemize}\n' )

    def visit_enumerated_list(self, node):
        self.body.append('\\begin{enumerate}\n' )
        if 'start' in node:
            self.body.append('\\setcounter{enumi}{%d}\n' % (node['start'] - 1))
        if self.table:
            self.table.has_problematic = True
    def depart_enumerated_list(self, node):
        self.body.append('\\end{enumerate}\n' )

    def visit_list_item(self, node):
        # Append "{}" in case the next character is "[", which would break
        # LaTeX's list environment (no numbering and the "[" is not printed).
        self.body.append(r'\item {} ')
    def depart_list_item(self, node):
        self.body.append('\n')

    def visit_definition_list(self, node):
        self.body.append('\\begin{description}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_definition_list(self, node):
        self.body.append('\\end{description}\n')

    def visit_definition_list_item(self, node):
        pass
    def depart_definition_list_item(self, node):
        pass

    def visit_term(self, node):
        ctx = '}] \\leavevmode'
        if node.get('ids'):
            ctx += self.hypertarget(node['ids'][0])
        self.body.append('\\item[{')
        self.context.append(ctx)
    def depart_term(self, node):
        self.body.append(self.context.pop())

    def visit_termsep(self, node):
        self.body.append(', ')
        raise nodes.SkipNode

    def visit_classifier(self, node):
        self.body.append('{[}')
    def depart_classifier(self, node):
        self.body.append('{]}')

    def visit_definition(self, node):
        pass
    def depart_definition(self, node):
        self.body.append('\n')

    def visit_field_list(self, node):
        self.body.append('\\begin{quote}\\begin{description}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_field_list(self, node):
        self.body.append('\\end{description}\\end{quote}\n')

    def visit_field(self, node):
        pass
    def depart_field(self, node):
        pass

    visit_field_name = visit_term
    depart_field_name = depart_term

    visit_field_body = visit_definition
    depart_field_body = depart_definition

    def visit_paragraph(self, node):
        self.body.append('\n')
    def depart_paragraph(self, node):
        self.body.append('\n')

    def visit_centered(self, node):
        self.body.append('\n\\begin{center}')
        if self.table:
            self.table.has_problematic = True
    def depart_centered(self, node):
        self.body.append('\n\\end{center}')

    def visit_hlist(self, node):
        # for now, we don't support a more compact list format
        # don't add individual itemize environments, but one for all columns
        self.compact_list += 1
        self.body.append('\\begin{itemize}\\setlength{\\itemsep}{0pt}'
                         '\\setlength{\\parskip}{0pt}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_hlist(self, node):
        self.compact_list -= 1
        self.body.append('\\end{itemize}\n')

    def visit_hlistcol(self, node):
        pass
    def depart_hlistcol(self, node):
        pass

    def latex_image_length(self, width_str):
        match = re.match('(\d*\.?\d*)\s*(\S*)', width_str)
        if not match:
            # fallback
            return width_str
        res = width_str
        amount, unit = match.groups()[:2]
        if not unit or unit == "px":
            # pixels: let LaTeX alone
            return None
        elif unit == "%":
            res = "%.3f\\linewidth" % (float(amount) / 100.0)
        return res

    def is_inline(self, node):
        """Check whether a node represents an inline element."""
        return isinstance(node.parent, nodes.TextElement)

    def visit_image(self, node):
        attrs = node.attributes
        pre = []                        # in reverse order
        post = []
        include_graphics_options = []
        is_inline = self.is_inline(node)
        if 'scale' in attrs:
            # Could also be done with ``scale`` option to
            # ``\includegraphics``; doing it this way for consistency.
            pre.append('\\scalebox{%f}{' % (attrs['scale'] / 100.0,))
            post.append('}')
        if 'width' in attrs:
            w = self.latex_image_length(attrs['width'])
            if w:
                include_graphics_options.append('width=%s' % w)
        if 'height' in attrs:
            h = self.latex_image_length(attrs['height'])
            if h:
                include_graphics_options.append('height=%s' % h)
        if 'align' in attrs:
            align_prepost = {
                # By default latex aligns the top of an image.
                (1, 'top'): ('', ''),
                (1, 'middle'): ('\\raisebox{-0.5\\height}{', '}'),
                (1, 'bottom'): ('\\raisebox{-\\height}{', '}'),
                (0, 'center'): ('{\\hfill', '\\hfill}'),
                # These 2 don't exactly do the right thing.  The image should
                # be floated alongside the paragraph.  See
                # http://www.w3.org/TR/html4/struct/objects.html#adef-align-IMG
                (0, 'left'): ('{', '\\hfill}'),
                (0, 'right'): ('{\\hfill', '}'),}
            try:
                pre.append(align_prepost[is_inline, attrs['align']][0])
                post.append(align_prepost[is_inline, attrs['align']][1])
            except KeyError:
                pass
        if not is_inline:
            pre.append('\n')
            post.append('\n')
        pre.reverse()
        if node['uri'] in self.builder.images:
            uri = self.builder.images[node['uri']]
        else:
            # missing image!
            if self.ignore_missing_images:
                return
            uri = node['uri']
        if uri.find('://') != -1:
            # ignore remote images
            return
        self.body.extend(pre)
        options = ''
        if include_graphics_options:
            options = '[%s]' % ','.join(include_graphics_options)
        self.body.append('\\includegraphics%s{%s}' % (options, uri))
        self.body.extend(post)
    def depart_image(self, node):
        pass

    def visit_figure(self, node):
        ids = ''
        for id in self.next_figure_ids:
            ids += self.hypertarget(id, anchor=False)
        self.next_figure_ids.clear()
        if 'width' in node and node.get('align', '') in ('left', 'right'):
            self.body.append('\\begin{wrapfigure}{%s}{%s}\n\\centering' %
                             (node['align'] == 'right' and 'r' or 'l',
                              node['width']))
            self.context.append(ids + '\\end{wrapfigure}\n')
        else:
            if (not 'align' in node.attributes or
                node.attributes['align'] == 'center'):
                # centering does not add vertical space like center.
                align = '\n\\centering'
                align_end = ''
            else:
                # TODO non vertical space for other alignments.
                align = '\\begin{flush%s}' % node.attributes['align']
                align_end = '\\end{flush%s}' % node.attributes['align']
            self.body.append('\\begin{figure}[%s]%s\n' % (
                self.elements['figure_align'], align))
            if any(isinstance(child, nodes.caption) for child in node):
                self.body.append('\\capstart\n')
            self.context.append(ids + align_end + '\\end{figure}\n')
    def depart_figure(self, node):
        self.body.append(self.context.pop())

    def visit_caption(self, node):
        self.in_caption += 1
        self.body.append('\\caption{')
    def depart_caption(self, node):
        self.body.append('}')
        self.in_caption -= 1

    def visit_legend(self, node):
        self.body.append('{\\small ')
    def depart_legend(self, node):
        self.body.append('}')

    def visit_admonition(self, node):
        self.body.append('\n\\begin{notice}{note}')
    def depart_admonition(self, node):
        self.body.append('\\end{notice}\n')

    def _make_visit_admonition(name):
        def visit_admonition(self, node):
            self.body.append(u'\n\\begin{notice}{%s}{%s:}' %
                             (name, admonitionlabels[name]))
        return visit_admonition
    def _depart_named_admonition(self, node):
        self.body.append('\\end{notice}\n')

    visit_attention = _make_visit_admonition('attention')
    depart_attention = _depart_named_admonition
    visit_caution = _make_visit_admonition('caution')
    depart_caution = _depart_named_admonition
    visit_danger = _make_visit_admonition('danger')
    depart_danger = _depart_named_admonition
    visit_error = _make_visit_admonition('error')
    depart_error = _depart_named_admonition
    visit_hint = _make_visit_admonition('hint')
    depart_hint = _depart_named_admonition
    visit_important = _make_visit_admonition('important')
    depart_important = _depart_named_admonition
    visit_note = _make_visit_admonition('note')
    depart_note = _depart_named_admonition
    visit_tip = _make_visit_admonition('tip')
    depart_tip = _depart_named_admonition
    visit_warning = _make_visit_admonition('warning')
    depart_warning = _depart_named_admonition

    def visit_versionmodified(self, node):
        pass
    def depart_versionmodified(self, node):
        pass

    def visit_target(self, node):
        def add_target(id):
            # indexing uses standard LaTeX index markup, so the targets
            # will be generated differently
            if id.startswith('index-'):
                return
            # do not generate \phantomsection in \section{}
            anchor = not self.in_title
            self.body.append(self.hypertarget(id, anchor=anchor))

        # postpone the labels until after the sectioning command
        parindex = node.parent.index(node)
        try:
            try:
                next = node.parent[parindex+1]
            except IndexError:
                # last node in parent, look at next after parent
                # (for section of equal level) if it exists
                if node.parent.parent is not None:
                    next = node.parent.parent[
                        node.parent.parent.index(node.parent)]
                else:
                    raise
            if isinstance(next, nodes.section):
                if node.get('refid'):
                    self.next_section_ids.add(node['refid'])
                self.next_section_ids.update(node['ids'])
                return
            elif isinstance(next, nodes.figure):
                # labels for figures go in the figure body, not before
                if node.get('refid'):
                    self.next_figure_ids.add(node['refid'])
                self.next_figure_ids.update(node['ids'])
                return
            elif isinstance(next, nodes.table):
                # same for tables, but only if they have a caption
                for n in node:
                    if isinstance(n, nodes.title):
                        if node.get('refid'):
                            self.next_table_ids.add(node['refid'])
                        self.next_table_ids.update(node['ids'])
                        return
        except IndexError:
            pass
        if 'refuri' in node:
            return
        if node.get('refid'):
            add_target(node['refid'])
        for id in node['ids']:
            add_target(id)
    def depart_target(self, node):
        pass

    def visit_attribution(self, node):
        self.body.append('\n\\begin{flushright}\n')
        self.body.append('---')
    def depart_attribution(self, node):
        self.body.append('\n\\end{flushright}\n')

    def visit_index(self, node, scre=re.compile(r';\s*')):
        if not node.get('inline', True):
            self.body.append('\n')
        entries = node['entries']
        for type, string, tid, ismain in entries:
            m = ''
            if ismain:
                m = '|textbf'
            try:
                if type == 'single':
                    p = scre.sub('!', self.encode(string))
                    self.body.append(r'\index{%s%s}' % (p, m))
                elif type == 'pair':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'pair', string)]
                    self.body.append(r'\index{%s!%s%s}\index{%s!%s%s}' %
                                     (p1, p2, m,  p2, p1, m))
                elif type == 'triple':
                    p1, p2, p3 = [self.encode(x)
                                  for x in split_into(3, 'triple', string)]
                    self.body.append(
                        r'\index{%s!%s %s%s}\index{%s!%s, %s%s}'
                        r'\index{%s!%s %s%s}' %
                        (p1, p2, p3, m,  p2, p3, p1, m,  p3, p1, p2, m))
                elif type == 'see':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'see', string)]
                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
                elif type == 'seealso':
                    p1, p2 = [self.encode(x) for x in split_into(2, 'seealso', string)]
                    self.body.append(r'\index{%s|see{%s}}' % (p1, p2))
                else:
                    self.builder.warn(
                        'unknown index entry type %s found' % type)
            except ValueError as err:
                self.builder.warn(str(err))
        raise nodes.SkipNode

    def visit_raw(self, node):
        if 'latex' in node.get('format', '').split():
            self.body.append(node.astext())
        raise nodes.SkipNode

    def visit_reference(self, node):
        for id in node.get('ids'):
            self.body += self.hypertarget(id, anchor=True)
        uri = node.get('refuri', '')
        if not uri and node.get('refid'):
            uri = '%' + self.curfilestack[-1] + '#' + node['refid']
        if self.in_title or not uri:
            self.context.append('')
        elif uri.startswith('mailto:') or uri.startswith('http:') or \
                 uri.startswith('https:') or uri.startswith('ftp:'):
            self.body.append('\\href{%s}{' % self.encode_uri(uri))
            # if configured, put the URL after the link
            show_urls = self.builder.config.latex_show_urls
            if node.astext() != uri and show_urls and show_urls != 'no':
                if uri.startswith('mailto:'):
                    uri = uri[7:]
                if show_urls == 'footnote' and not \
                   (self.in_footnote or self.in_caption):
                    # obviously, footnotes in footnotes are not going to work
                    self.context.append(
                        r'}\footnote{%s}' % self.encode_uri(uri))
                else:  # all other true values (b/w compat)
                    self.context.append('} (%s)' % self.encode_uri(uri))
            else:
                self.context.append('}')
        elif uri.startswith('#'):
            # references to labels in the same document
            id = self.curfilestack[-1] + ':' + uri[1:]
            self.body.append(self.hyperlink(id))
            if self.builder.config.latex_show_pagerefs and not \
                    self.in_production_list:
                self.context.append('}} (%s)' % self.hyperpageref(id))
            else:
                self.context.append('}}')
        elif uri.startswith('%'):
            # references to documents or labels inside documents
            hashindex = uri.find('#')
            if hashindex == -1:
                # reference to the document
                id = uri[1:] + '::doc'
            else:
                # reference to a label
                id = uri[1:].replace('#', ':')
            self.body.append(self.hyperlink(id))
            if len(node) and hasattr(node[0], 'attributes') and \
                   'std-term' in node[0].get('classes', []):
                # don't add a pageref for glossary terms
                self.context.append('}}')
            else:
                if self.builder.config.latex_show_pagerefs:
                    self.context.append('}} (%s)' % self.hyperpageref(id))
                else:
                    self.context.append('}}')
        else:
            self.builder.warn('unusable reference target found: %s' % uri,
                              (self.curfilestack[-1], node.line))
            self.context.append('')
    def depart_reference(self, node):
        self.body.append(self.context.pop())

    def visit_download_reference(self, node):
        pass
    def depart_download_reference(self, node):
        pass

    def visit_pending_xref(self, node):
        pass
    def depart_pending_xref(self, node):
        pass

    def visit_emphasis(self, node):
        self.body.append(r'\emph{')
    def depart_emphasis(self, node):
        self.body.append('}')

    def visit_literal_emphasis(self, node):
        self.body.append(r'\emph{\texttt{')
        self.no_contractions += 1
    def depart_literal_emphasis(self, node):
        self.body.append('}}')
        self.no_contractions -= 1

    def visit_strong(self, node):
        self.body.append(r'\textbf{')
    def depart_strong(self, node):
        self.body.append('}')

    def visit_literal_strong(self, node):
        self.body.append(r'\textbf{\texttt{')
        self.no_contractions += 1
    def depart_literal_strong(self, node):
        self.body.append('}}')
        self.no_contractions -= 1

    def visit_abbreviation(self, node):
        abbr = node.astext()
        self.body.append(r'\textsc{')
        # spell out the explanation once
        if node.hasattr('explanation') and abbr not in self.handled_abbrs:
            self.context.append('} (%s)' % self.encode(node['explanation']))
            self.handled_abbrs.add(abbr)
        else:
            self.context.append('}')
    def depart_abbreviation(self, node):
        self.body.append(self.context.pop())

    def visit_title_reference(self, node):
        self.body.append(r'\emph{')
    def depart_title_reference(self, node):
        self.body.append('}')

    def visit_citation(self, node):
        # TODO maybe use cite bibitems
        # bibitem: [citelabel, citetext, docname, citeid]
        self.bibitems.append(['', '', '', ''])
        self.context.append(len(self.body))
    def depart_citation(self, node):
        size = self.context.pop()
        text = ''.join(self.body[size:])
        del self.body[size:]
        self.bibitems[-1][1] = text

    def visit_citation_reference(self, node):
        # This is currently never encountered, since citation_reference nodes
        # are already replaced by pending_xref nodes in the environment.
        self.body.append('\\cite{%s}' % self.idescape(node.astext()))
        raise nodes.SkipNode

    def visit_literal(self, node):
        self.no_contractions += 1
        if self.in_title:
            self.body.append(r'\texttt{')
        else:
            self.body.append(r'\code{')
    def depart_literal(self, node):
        self.no_contractions -= 1
        self.body.append('}')

    def visit_footnote_reference(self, node):
        num = node.astext().strip()
        try:
            footnode, used = self.footnotestack[-1][num]
        except (KeyError, IndexError):
            raise nodes.SkipNode
        # if a footnote has been inserted once, it shouldn't be repeated
        # by the next reference
        if used:
            self.body.append('\\footnotemark[%s]' % num)
        else:
            if self.in_caption:
                raise UnsupportedError('%s:%s: footnotes in float captions '
                                       'are not supported by LaTeX' %
                                       (self.curfilestack[-1], node.line))
            footnode.walkabout(self)
            self.footnotestack[-1][num][1] = True
        raise nodes.SkipChildren
    def depart_footnote_reference(self, node):
        pass

    def visit_literal_block(self, node):
        if self.in_footnote:
            raise UnsupportedError('%s:%s: literal blocks in footnotes are '
                                   'not supported by LaTeX' %
                                   (self.curfilestack[-1], node.line))
        if node.rawsource != node.astext():
            # most probably a parsed-literal block -- don't highlight
            self.body.append('\\begin{alltt}\n')
        else:
            code = node.astext().rstrip('\n')
            lang = self.hlsettingstack[-1][0]
            linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
            highlight_args = node.get('highlight_args', {})
            if 'language' in node:
                # code-block directives
                lang = node['language']
                highlight_args['force'] = True
            if 'linenos' in node:
                linenos = node['linenos']
            caption = node.get('caption')
            if caption:
                self.body.append('\n{\\colorbox[rgb]{0.9,0.9,0.9}'
                                 '{\\makebox[\\textwidth][l]'
                                 '{\\small\\texttt{%s}}}}\n' % (caption,))
            def warner(msg):
                self.builder.warn(msg, (self.curfilestack[-1], node.line))
            hlcode = self.highlighter.highlight_block(code, lang, warn=warner,
                    linenos=linenos, **highlight_args)
            # workaround for Unicode issue
            hlcode = hlcode.replace(u'€', u'@texteuro[]')
            # must use original Verbatim environment and "tabular" environment
            if self.table:
                hlcode = hlcode.replace('\\begin{Verbatim}',
                                        '\\begin{OriginalVerbatim}')
                self.table.has_problematic = True
                self.table.has_verbatim = True
            # get consistent trailer
            hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
            hlcode = hlcode.rstrip() + '\n'
            self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
                             (self.table and 'Original' or ''))
            raise nodes.SkipNode
    def depart_literal_block(self, node):
        self.body.append('\n\\end{alltt}\n')
    visit_doctest_block = visit_literal_block
    depart_doctest_block = depart_literal_block

    def visit_line(self, node):
        self.body.append('\item[] ')
    def depart_line(self, node):
        self.body.append('\n')

    def visit_line_block(self, node):
        if isinstance(node.parent, nodes.line_block):
            self.body.append('\\item[]\n'
                             '\\begin{DUlineblock}{\\DUlineblockindent}\n')
        else:
            self.body.append('\n\\begin{DUlineblock}{0em}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_line_block(self, node):
        self.body.append('\\end{DUlineblock}\n')

    def visit_block_quote(self, node):
        # If the block quote contains a single object and that object
        # is a list, then generate a list not a block quote.
        # This lets us indent lists.
        done = 0
        if len(node.children) == 1:
            child = node.children[0]
            if isinstance(child, nodes.bullet_list) or \
                    isinstance(child, nodes.enumerated_list):
                done = 1
        if not done:
            self.body.append('\\begin{quote}\n')
            if self.table:
                self.table.has_problematic = True
    def depart_block_quote(self, node):
        done = 0
        if len(node.children) == 1:
            child = node.children[0]
            if isinstance(child, nodes.bullet_list) or \
                    isinstance(child, nodes.enumerated_list):
                done = 1
        if not done:
            self.body.append('\\end{quote}\n')

    # option node handling copied from docutils' latex writer

    def visit_option(self, node):
        if self.context[-1]:
            # this is not the first option
            self.body.append(', ')
    def depart_option(self, node):
        # flag that the first option is done.
        self.context[-1] += 1

    def visit_option_argument(self, node):
        """The delimiter betweeen an option and its argument."""
        self.body.append(node.get('delimiter', ' '))
    def depart_option_argument(self, node):
        pass

    def visit_option_group(self, node):
        self.body.append('\\item [')
        # flag for first option
        self.context.append(0)
    def depart_option_group(self, node):
        self.context.pop() # the flag
        self.body.append('] ')

    def visit_option_list(self, node):
        self.body.append('\\begin{optionlist}{3cm}\n')
        if self.table:
            self.table.has_problematic = True
    def depart_option_list(self, node):
        self.body.append('\\end{optionlist}\n')

    def visit_option_list_item(self, node):
        pass
    def depart_option_list_item(self, node):
        pass

    def visit_option_string(self, node):
        ostring = node.astext()
        self.no_contractions += 1
        self.body.append(self.encode(ostring))
        self.no_contractions -= 1
        raise nodes.SkipNode

    def visit_description(self, node):
        self.body.append(' ')
    def depart_description(self, node):
        pass

    def visit_superscript(self, node):
        self.body.append('$^{\\text{')
    def depart_superscript(self, node):
        self.body.append('}}$')

    def visit_subscript(self, node):
        self.body.append('$_{\\text{')
    def depart_subscript(self, node):
        self.body.append('}}$')

    def visit_substitution_definition(self, node):
        raise nodes.SkipNode

    def visit_substitution_reference(self, node):
        raise nodes.SkipNode

    def visit_inline(self, node):
        classes = node.get('classes', [])
        self.body.append(r'\DUspan{%s}{' % ','.join(classes))
    def depart_inline(self, node):
        self.body.append('}')

    def visit_generated(self, node):
        pass
    def depart_generated(self, node):
        pass

    def visit_compound(self, node):
        pass
    def depart_compound(self, node):
        pass

    def visit_container(self, node):
        pass
    def depart_container(self, node):
        pass

    def visit_decoration(self, node):
        pass
    def depart_decoration(self, node):
        pass

    # docutils-generated elements that we don't support

    def visit_header(self, node):
        raise nodes.SkipNode

    def visit_footer(self, node):
        raise nodes.SkipNode

    def visit_docinfo(self, node):
        raise nodes.SkipNode

    # text handling

    def encode(self, text):
        text = text_type(text).translate(tex_escape_map)
        if self.literal_whitespace:
            # Insert a blank before the newline, to avoid
            # ! LaTeX Error: There's no line here to end.
            text = text.replace(u'\n', u'~\\\\\n').replace(u' ', u'~')
        if self.no_contractions:
            text = text.replace('--', u'-{-}')
            text = text.replace("''", u"'{'}")
        return text

    def encode_uri(self, text):
        # in \href, the tilde is allowed and must be represented literally
        return self.encode(text).replace('\\textasciitilde{}', '~')

    def visit_Text(self, node):
        text = self.encode(node.astext())
        if not self.no_contractions:
            text = educate_quotes_latex(text)
        self.body.append(text)
    def depart_Text(self, node):
        pass

    def visit_comment(self, node):
        raise nodes.SkipNode

    def visit_meta(self, node):
        # only valid for HTML
        raise nodes.SkipNode

    def visit_system_message(self, node):
        pass
    def depart_system_message(self, node):
        self.body.append('\n')

    def visit_math(self, node):
        self.builder.warn('using "math" markup without a Sphinx math extension '
                          'active, please use one of the math extensions '
                          'described at http://sphinx-doc.org/ext/math.html',
                          (self.curfilestack[-1], node.line))
        raise nodes.SkipNode

    visit_math_block = visit_math

    def unknown_visit(self, node):
        raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
